---
title: Authorization in the GraphOS Router
subtitle: Enforce authorization with schema directives
description: Enforce authorization in the GraphOS Router with the @requireScopes, @authenticated, and @policy directives.
---

<PlanRequired plans={["Free", "Developer", "Standard", "Enterprise"]}>

Rate limits apply on the Free plan.
Developer and Standard plans require Router v2.6.0 or later.

</PlanRequired>

Learn how to secure access to your graph via the router by using authorization directives in your GraphQL schemas.

## Prerequisites

<Note>

Only the GraphOS Router supports authorization directives&mdash;[`@apollo/gateway`](/federation/v1/gateway/) does _not_. Check out the [migration guide](/router/migrating-from-gateway/) if you'd like to use them.

</Note>

Before using the authorization directives in your subgraph schemas, you must:

- Validate that your GraphOS Router uses version `1.29.1` or later and is [connected to your GraphOS Enterprise organization](/router/enterprise-features/#enabling-enterprise-features)
- Include **[claims](#configure-request-claims)** in requests made to the router (for `@authenticated` and `@requiresScopes`)

### Configure request claims

Claims are the individual details of a request's authentication and scope. They might include details like the ID of the user making the request and any authorization scopes&mdash;for example, `read:profiles`&mdash; assigned to that user. The authorization directives use a request's claims to evaluate which fields and types are authorized.

To provide the router with the claims it needs, you must either configure JSON Web Token (JWT) authentication or add an external coprocessor that adds claims to a request's context. In some cases (explained below), you may require both.

- **JWT authentication configuration**: If you configure [JWT authentication](/router/configuration/authn-jwt), the GraphOS Router [automatically adds a JWT token's claims](/router/configuration/authn-jwt#working-with-jwt-claims) to the request's context at the `apollo::authentication::jwt_claims` key.
- **Adding claims via coprocessor**: If you can't use JWT authentication, you can [add claims with a coprocessor](/router/customizations/coprocessor#adding-authorization-claims-via-coprocessor). Coprocessors let you hook into the GraphOS Router's request-handling lifecycle with custom code.
- **Augmenting JWT claims via coprocessor**: Your authorization policies may require information beyond what your JSON web tokens provide. For example, a token's claims may include user IDs, which you then use to look up user roles. For situations like this, you can [augment the claims](/router/configuration/authn-jwt#claim-augmentation-via-coprocessors) from your JSON web tokens with coprocessors.

## Authorization directives

Authorization directives are turned on by default. To disable them, include the following in your router's [YAML config file](/router/configuration/overview/):

```yaml title="router.yaml"
authorization:
  directives:
    enabled: false
```

### `@requiresScopes`

<MinVersionBadge version="Router v1.29.1" />

The `@requiresScopes` directive marks fields and types as restricted based on required scopes.
The directive includes a `scopes` argument with an array of the required scopes to declare which scopes are required:

```graphql
@requiresScopes(scopes: [["scope1", "scope2", "scope3"]])
```

<Tip>

Use `@requiresScopes` when access to a field or type depends only on claims associated with a claims object or access token.

If your authorization validation logic or data are more complex&mdash;such as checking specific values in headers or looking up data from other sources such as databases&mdash;and aren't solely based on a claims object or access token, use [`@policy`](#policy) instead.

</Tip>

Depending on the scopes present on the request, the router filters out unauthorized fields and types.

> You can use Boolean logic to define the required scopes. See [Combining required scopes](#combining-required-scopes-with-andor-logic) for details.

The directive validates the required scopes by loading the claims object at the `apollo::authentication::jwt_claims` key in a request's context.
The claims object's `scope` key's value should be a space-separated string of scopes in the format defined by the [OAuth2 RFC for access token scopes](https://datatracker.ietf.org/doc/html/rfc6749#section-3.3).

```rhai
claims = context["apollo::authentication::jwt_claims"]
claims["scope"] = "scope1 scope2 scope3"
```

<ExpansionPanel title="What if my request scopes aren't in OAuth2 format?">

If the `apollo::authentication::jwt_claims` object holds scopes in another format, for example, an array of strings, or at a key other than `"scope"`, you can edit the claims with a [Rhai script](/graphos/routing/customization/rhai).

The example below extracts an array of scopes from the `"roles"` claim and reformats them as a space-separated string.

```Rhai
fn router_service(service) {
  let request_callback = |request| {
    let claims = request.context["apollo::authentication::jwt_claims"];
    let roles = claims["roles"];

    let scope = "";
    if roles.len() > 1 {
      scope = roles[0];
    }

    if roles.len() > 2 {
      for i in 1..roles.len() {
        scope += ' ';
        scope += roles[i];
      }
    }

    claims["scope"] = scope;
    request.context["apollo::authentication::jwt_claims"] = claims;
  };
  service.map_request(request_callback);
}
```

</ExpansionPanel>

#### Usage

<Caution>

    Federation v2.9 and above doesn't allow `@requiresScopes` on interfaces. Applying `@requiresScopes` to an interface or interface field results in a composition error.

</Caution>

To use the `@requiresScopes` directive in a subgraph, you can [import it from the `@link` directive](/federation/federated-types/federated-directives/#importing-directives) like so:

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.5",
    import: [..., "@requiresScopes"])
```

It is defined as follows:

```graphql
scalar federation__Scope
directive @requiresScopes(
  scopes: [[federation__Scope!]!]!
) on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM
```

#### Combining required scopes with `AND`/`OR` logic

A request must include _all_ elements in the inner-level `scopes` array to resolve the associated field or type. In other words, the authorization validation uses **AND** logic between the elements in the inner-level `scopes` array.

```graphql
@requiresScopes(scopes: [["scope1", "scope2", "scope3"]])
```

For the preceding example, a request would need `scope1` **AND** `scope2` **AND** `scope3` to be authorized.

You can use nested arrays to introduce **OR** logic:

```graphql
@requiresScopes(scopes: [["scope1"], ["scope2"], ["scope3"]])
```

For the preceding example, a request would need `scope1` **OR** `scope2` **OR** `scope3` to be authorized.

You can nest arrays and elements as needed to achieve your desired logic. For example:

```graphql
@requiresScopes(scopes: [["scope1", "scope2"], ["scope3"]])
```

This syntax requires requests to have either (`scope1` **AND** `scope2`) **OR** just `scope3` to be authorized.

#### Example `@requiresScopes` use case

Imagine the social media platform you're building lets users view other users' information only if they have the required permissions.
Your schema may look like this:

```graphql
type Query {
  user(id: ID!): User @requiresScopes(scopes: [["read:others"]]) #highlight-line
  users: [User!]! @requiresScopes(scopes: [["read:others"]]) #highlight-line
  post(id: ID!): Post
}

type User {
  id: ID!
  username: String
  email: String @requiresScopes(scopes: [["read:email"]]) #highlight-line
  profileImage: String
  posts: [Post!]!
}

type Post {
  id: ID!
  author: User!
  title: String!
  content: String!
}
```

Depending on a request's attached scopes, the router executes the following query differently.
If the request includes only the `read:others` scope, then the router executes the following filtered query:

<CodeColumns>

```graphql title="Raw query to router"
query {
  users {
    username
    profileImage
    email
  }
}
```

```graphql title="Scopes: 'read:others'"
query {
  users {
    username
    profileImage
  }
}
```

</CodeColumns>

The response would include an error at the `/users/@/email` path since that field requires the `read:emails` scope.
The router can execute the entire query successfully if the request includes the `read:others read:emails` scope set.

The router returns `null` for unauthorized fields and applies the [standard GraphQL null propagation rules](https://www.apollographql.com/blog/graphql/basics/using-nullability-in-graphql/#what-happens-if-you-try-to-return-null-for-a-non-null-field).

```json title="Unauthorized request response"
{
  "data": {
    "me": null,
    "post": {
      "title": "Securing supergraphs"
    }
  },
  "errors": [
    {
      "message": "Unauthorized field or type",
      "path": ["me"],
      "extensions": {
        "code": "UNAUTHORIZED_FIELD_OR_TYPE"
      }
    },
    {
      "message": "Unauthorized field or type",
      "path": ["post", "views"],
      "extensions": {
        "code": "UNAUTHORIZED_FIELD_OR_TYPE"
      }
    }
  ]
}
```

### `@authenticated`

<MinVersionBadge version="Router v1.29.1" />

The `@authenticated` directive marks specific fields and types as requiring authentication.
It works by checking for the `apollo::authentication::jwt_claims` key in a request's context, that is added either by the JWT authentication plugin, when the request contains a valid JWT, or by an authentication coprocessor.
If the key exists, it means the request is authenticated, and the router executes the query in its entirety.
If the request is unauthenticated, the router removes `@authenticated` fields before planning the query and only executes the parts of the query that don't require authentication.

#### Usage

<Caution>

    Federation v2.9 and above doesn't allow `@authenticated` on interfaces. Applying `@authenticated` to an interface or interface field results in a composition error.

</Caution>

To use the `@authenticated` directive in a subgraph, you can [import it from the `@link` directive](/federation/federated-types/federated-directives/#importing-directives) like so:

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.5",
    import: [..., "@authenticated"])
```

It is defined as follows:

```graphql
directive @authenticated on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM
```

#### Example `@authenticated` use case

Diving deeper into the [social media example](#example-requiresscopes-use-case): let's say unauthenticated users can view a post's title, author, and content.
However, you only want authenticated users to see the number of views a post has received.
You also need to be able to query for an authenticated user's information.

The relevant part of your schema may look like this:

```graphql
type Query {
  me: User @authenticated #highlight-line
  post(id: ID!): Post
}

type User {
  id: ID!
  username: String
  email: String @requiresScopes(scopes: [["read:email"]])
  posts: [Post!]!
}

type Post {
  id: ID!
  author: User!
  title: String!
  content: String!
  views: Int @authenticated #highlight-line
}
```

Consider the following query:

```graphql title="Sample query"
query {
  me {
    username
  }
  post(id: "1234") {
    title
    views
  }
}
```

The router would execute the entire query for an authenticated request.
For an unauthenticated request, the router would remove the `@authenticated` fields and execute the filtered query.

<CodeColumns>

```graphql title="Query executed for an authenticated request"
query {
  me {
    username
  }
  post(id: "1234") {
    title
    views
  }
}
```

```graphql title="Query executed for an unauthenticated request"
query {
  post(id: "1234") {
    title
  }
}
```

</CodeColumns>

For an unauthenticated request, the router doesn't attempt to resolve the top-level `me` query, nor the views for the post with `id: "1234"`.
The response retains the initial request's shape but returns `null` for unauthorized fields and applies the [standard GraphQL null propagation rules](https://www.apollographql.com/blog/graphql/basics/using-nullability-in-graphql/#what-happens-if-you-try-to-return-null-for-a-non-null-field).

```json title="Unauthenticated request response"
{
  "data": {
    "me": null,
    "post": {
      "title": "Securing supergraphs"
    }
  },
  "errors": [
    {
      "message": "Unauthorized field or type",
      "path": ["me"],
      "extensions": {
        "code": "UNAUTHORIZED_FIELD_OR_TYPE"
      }
    },
    {
      "message": "Unauthorized field or type",
      "path": ["post", "views"],
      "extensions": {
        "code": "UNAUTHORIZED_FIELD_OR_TYPE"
      }
    }
  ]
}
```

If _every_ requested field requires authentication and a request is unauthenticated, the router generates an error indicating that the query is unauthorized.

### `@policy`

<MinVersionBadge version="Router v1.35.0" />

The `@policy` directive marks fields and types as restricted based on authorization policies evaluated in a [Rhai script](/graphos/routing/customization/rhai/) or [coprocessor](/router/customizations/coprocessor). This enables custom authorization validation beyond authentication and scopes. It is useful when we need more complex policy evaluation than verifying the presence of a claim value in a list (example: checking specific values in headers).

<Tip>

If access to a field or type is restricted solely by the claims associated with a claims object or access token, consider using [`@requiresScopes`](#requiresscopes) instead.

</Tip>

The `@policy` directive includes a `policies` argument that defines an array of the required policies that are a list of strings with no formatting constraints. In general you can use the strings as arguments for any format you like. The following example shows a policy that might require the support role:

```graphql
@policy(policies: [["roles:support"]])
```

Using the `@policy` directive requires a [Supergraph plugin](/router/customizations/overview) to evaluate the authorization policies. This is useful to bridge router authorization with an existing authorization stack or link policy execution with lookups in a database.

An overview of how `@policy` is processed through the router's request lifecycle:

- At the [`RouterService` level](/graphos/routing/request-lifecycle), the GraphOS Router extracts the list of policies relevant to a request from the schema and then stores them in the request's context in `apollo::authorization::required_policies` as a map `policy -> null|true|false`.

- At the `SupergraphService` level, you must provide a Rhai script or coprocessor to evaluate the map.
  If the policy is validated, the script or coprocessor should set its value to `true` or otherwise set it to `false`. If the value is left to `null`, it will be treated as `false` by the router. Afterward, the router filters the requests' types and fields to only those where the policy is `true`.

- If no field of a subgraph query passes its authorization policies, the router stops further processing of the query and precludes unauthorized subgraph requests. This efficiency gain is a key benefit of the `@policy` and other authorization directives.

#### Usage

<Caution>

    Federation v2.9 and above doesn't allow `@policy` on interfaces. Applying `@policy` to an interface or interface field results in a composition error.

</Caution>

To use the `@policy` directive in a subgraph, you can [import it from the `@link` directive](/federation/federated-types/federated-directives/#importing-directives) like so:

```graphql
extend schema
  @link(
    url: "https://specs.apollo.dev/federation/v2.6",
    import: [..., "@policy"])
```

The `@policy` directive is defined as follows:

```graphql
scalar federation__Policy
directive @policy(
  policies: [[federation__Policy!]!]!
) on OBJECT | FIELD_DEFINITION | INTERFACE | SCALAR | ENUM
```

Using the `@policy` directive requires a [Supergraph plugin](/router/customizations/overview) to evaluate the authorization policies. You can do this with a [Rhai script](/graphos/routing/customization/rhai/) or [coprocessor](/router/customizations/coprocessor). Refer to the following [example use case](#example-policy-use-case) for more information. (Although a [native plugin](/router/customizations/native) can also evaluate authorization policies, we don't recommend using it.)

#### Combining policies with `AND`/`OR` logic

Authorization validation uses **AND** logic between the elements in the inner-level `policies` array, where a request must include _all_ elements in the inner-level `policies` array to resolve the associated field or type. For the following example, a request would need `policy1` **AND** `policy2` **AND** `policy3` to be authorized:

```graphql
@policy(policies: [["policy1", "policy2", "policy3"]])
```

Alternatively, to introduce **OR** logic you can use nested arrays. For the following example, a request would need `policy1` **OR** `policy2` **OR** `policy3` to be authorized:

```graphql
@policy(policies: [["policy1"], ["policy2"], ["policy3"]])
```

You can nest arrays and elements as needed to achieve your desired logic. For the following example, its syntax requires requests to have either (`policy1` **AND** `policy2`) **OR** just `policy3` to be authorized:

```graphql
@policy(policies: [["policy1", "policy2"], ["policy3"]])
```

#### Example `@policy` use case

##### Usage with a coprocessor

Diving even deeper into the [social media example](#example-requiresscopes-use-case): suppose you want only a user to have access to their own profile and credit card information. Of the available authorization directives, you use `@policy` instead of `@requiresScopes` because the validation logic relies on more than the scopes of an access token.

You can add the authorization policies `read_profile` and `read_credit_card`. The relevant part of your schema may look like this:

```graphql
type Query {
  me: User @authenticated @policy(policies: [["read_profile"]]) #highlight-line
  post(id: ID!): Post
}

type User {
  id: ID!
  username: String
  email: String @requiresScopes(scopes: [["read:email"]])
  posts: [Post!]!
  credit_card: String @policy(policies: [["read_credit_card"]]) #highlight-line
}

type Post {
  id: ID!
  author: User!
  title: String!
  content: String!
  views: Int @authenticated
}
```

You can use a [coprocessor](/router/customizations/coprocessor) called at the Supergraph request stage to receive and execute the list of policies.

If you configure your router like this:

```yaml title="router.yaml"
coprocessor:
  url: http://127.0.0.1:8081
  supergraph:
    request:
      context: all
```

A coprocessor can then receive a request with this format:

```json
{
  "version": 1,
  "stage": "SupergraphRequest",
  "control": "continue",
  "id": "d0a8245df0efe8aa38a80dba1147fb2e",
  "context": {
    "entries": {
      "apollo::authentication::jwt_claims": {
        "exp": 10000000000,
        "sub": "457f6bb6-789c-4e8b-8560-f3943a09e72a"
      },
      "apollo::authorization::required_policies": {
        "read_profile": null,
        "read_credit_card": null
      }
    }
  },
  "method": "POST"
}
```

A user can read their own profile, so `read_profile` will succeed. But only the billing system should be able to see the credit card, so `read_credit_card` will fail. The coprocessor will then return:

```json
{
  "version": 1,
  "stage": "SupergraphRequest",
  "control": "continue",
  "id": "d0a8245df0efe8aa38a80dba1147fb2e",
  "context": {
    "entries": {
      "apollo::authentication::jwt_claims": {
        "exp": 10000000000,
        "sub": "457f6bb6-789c-4e8b-8560-f3943a09e72a"
      },
      "apollo::authorization::required_policies": {
        "read_profile": true,
        "read_credit_card": false
      }
    }
  }
}
```

##### Usage with a Rhai script

For another example, suppose that you want to restrict access for posts to a support user. Given that the `policies` argument is a string, you can set it as a `"<key>:<value>"` format that a Rhai script can parse and evaluate.

The relevant part of your schema may look like this:

```graphql
type Query {
  me: User @policy(policies: [["kind:user"]]) #highlight-line
}

type User {
  id: ID!
  username: String @policy(policies: [["roles:support"]]) #highlight-line
}
```

You can then use the following Rhai script to parse and evaluate the `policies` string:

```rhai
fn supergraph_service(service) {
  let request_callback = |request| {
    let claims = request.context["apollo::authentication::jwt_claims"];
    let policies = request.context["apollo::authorization::required_policies"];

    if policies != () {
      for key in policies.keys() {
        let array = key.split(":");
        if array.len == 2 {
          switch array[0] {
            "kind" => {
              policies[key] = claims[`kind`] == array[1];
            }
            "roles" => {
              policies[key] = claims[`roles`].contains(array[1]);
            }
            _ => {}
          }
        }
      }
    }
    request.context["apollo::authorization::required_policies"] = policies;
  };
  service.map_request(request_callback);
}
```

#### Special case for subscriptions

When using subscriptions along with `@policy` authorization, subscription events restart from the execution service, which means that if the authorization status of the subscription session changed, then it cannot go through query planning again, and the session should be closed. To that end, the policies should be evaluated again at the execution service level, and if they changed, an error should be returned to stop the subscription.

## Composition and federation

GraphOS's composition strategy for authorization directives is intentionally accumulative. When you define authorization directives on fields and types in subgraphs, GraphOS composes them into the supergraph schema. In other words, if subgraph fields or types include `@requiresScopes`, `@authenticated`, or `@policy` directives, they are set on the supergraph too. Whether composition uses `AND` or `OR` logic depends on how the authorization directives are used.

### Composed fields with different authorization directives

If a shared field uses different authorization directives across subgraphs, composition merges them using `AND` logic.
For example, suppose the `me` query requires `@authenticated` in one subgraph and the `read:user` scope in another subgraph:

```graphql title="Subgraph A"
type Query {
  me: User @authenticated
}

type User {
  id: ID!
  username: String
  email: String
}
```

```graphql title="Subgraph B"
type Query {
  me: User @requiresScopes(scopes: [["read:user"]])
}

type User {
  id: ID!
  username: String
  email: String
}
```

A request must both be authenticated **AND** have the required `read:user` scope to succeed.

<Note>

Recall that the `@authenticated` directive only checks for the existence of the `apollo::authentication::jwt_claims` key in a request's context, so authentication is guaranteed if the request includes scopes.

</Note>

### Composed fields with the same authorization directives

If a shared field uses the same authorization directives across subgraphs, composition merges them using `AND` logic.
For example, suppose two subgraphs use the `@requiresScopes` directive on the `users` query.
One subgraph requires the `read:users` scope, and another subgraph requires the `read:profiles` scope:

```graphql title="Subgraph A"
type Query {
  users: [User!]! @requiresScopes(scopes: [["read:users"]])
}
```

```graphql title="Subgraph B"
type Query {
  users: [User!]! @requiresScopes(scopes: [["read:profiles"]])
}
```

A request would need both the `read:users` **AND** the `read:profiles` scope to be authorized.

```graphql title="Supergraph"
type Query {
  users: [User!]! @requiresScopes(scopes: [["read:users", "read:profiles"]])
}
```

<Tip>

Refer to the section on [Combining scopes with AND/OR logic](#combining-required-scopes-with-andor-logic) for a refresher of `@requiresScopes` boolean syntax.

</Tip>

#### Combining `AND`/`OR` logic with `@requiresScopes`

As with combining scopes for a single use of [`@requiresScopes`](#combining-required-scopes-with-andor-logic), you can use nested arrays to introduce **AND** and **OR** logic in a single subgraph:

```graphql title="Subgraph A"
type Query {
  users: [User!]! @requiresScopes(scopes: [["read:profile", "read:users"], ["read:admin"]])
}
```

```graphql title="Subgraph B"
type Query {
  users: [User!]! @requiresScopes(scopes: [["read:admin"]])
}
```

Since both subgraphs use the same authorization directive, composition [merges them using **AND** logic](#composed-fields-with-the-same-authorization-directives). This results in a scope requirement of either (`read:profile` AND `read:users` AND `read:admin`) OR `read:admin`. Since the `read:admin` scope by itself subsumes the other requirement, we drop the redundant requirements to arrive at the final scope:

```graphql title="Supergraph"
type Query {
  users: [User!]!
    @requiresScopes(scopes: [["read:admin"]])
}
```

### Authorization and `@key` fields

The [`@key` directive](/graphos/reference/federation/directives#key) lets you create an entity whose fields resolve across multiple subgraphs.
If you use authorization directives on fields defined in `@key` directives, Apollo still uses those fields to compose entities between the subgraphs, but the client cannot query them directly.

Consider these example subgraph schemas:

```graphql title="Product subgraph"
type Query {
  product: Product
}

type Product @key(fields: "id") {
  id: ID! @authenticated
  name: String!
  price: Int @authenticated
}
```

```graphql title="Inventory subgraph"
type Query {
  product: Product
}

type Product @key(fields: "id") {
  id: ID! @authenticated
  inStock: Boolean!
}
```

An unauthenticated request would successfully execute this query:

```graphql
query {
  product {
    name
    inStock
  }
}
```

Specifically, under the hood, the router would use the `id` field to resolve the `Product` entity, but it wouldn't return it.

For the following query, an unauthenticated request would resolve `null` for `id`. And since `id` is a non-nullable field, `product` would return `null`.

```graphql
query {
  product {
    id
    username
  }
}
```

This behavior resembles what you can create with [contracts](/graphos/delivery/contracts/) and the [`@inaccessible` directive](/graphos/reference/federation/directives#inaccessible).

### Authorization and interfaces

If a type [implementing an interface](/apollo-server/schema/unions-interfaces/#interface-type) requires authorization, unauthorized requests cannot query fields on the interface.

For example, consider this schema where the `Post` interface doesn't require authentication, but the `PrivateBlog` type, which implements `Post`, does:
 
```graphql {11, 18}
type Query {
  posts: [Post!]!
}

type User {
  id: ID!
  username: String
  posts: [Post!]!
}

interface Post {
  id: ID!
  author: User!
  title: String!
  content: String!
}

type PrivateBlog implements Post @authenticated {
  id: ID!
  author: User!
  title: String!
  content: String!
}
```

If an unauthenticated request sends a query for fields on the `Post` interface:

```graphql
query {
  posts {
    id
    author
    title
  }
}
```

The router would filter out the whole query.

The response would include an `"UNAUTHORIZED_FIELD_OR_TYPE"` error at the `/posts` path.

#### Polymorphic types with different requirements

Interface implementations can each specify different authorization requirements. To successfully query the interface's fields, the request must meet all of those authorization requirements, which are combined with `AND` logic.

For example, consider this schema where the `PublicBlog` type doesn't require authentication, but the `PrivateBlog` type does:

```graphql {11, 18, 25}
type Query {
  posts: [Post!]!
}

type User {
  id: ID!
  username: String
  posts: [Post!]!
}

interface Post {
  id: ID!
  author: User!
  title: String!
  content: String!
}

type PrivateBlog implements Post @authenticated {
  id: ID!
  author: User!
  title: String!
  content: String!
}

type PublicBlog implements Post {
  id: ID!
  author: User!
  title: String!
  content: String!
}
```

If an unauthenticated request sends a query for fields on the `Post` interface:

```graphql
query {
  posts {
    id
    author
    title
  }
}
```

The router would filter out the whole query. The response would include an `"UNAUTHORIZED_FIELD_OR_TYPE"` error at the `/posts` path. This is because the data returned could contain an instance of `PrivateBlog`, which requires authentication.

If an unauthenticated request specifically asks for `PublicBlog` and `PrivateBlog` entries:

```graphql
query {
  posts {
    ... on PublicBlog {
        id
        author
        title
    }
    ... on PrivateBlog {
        id
        author
        title
    }
  }
}
```

The router would filter out the authenticated parts of the query, leaving only the fields that don't require authentication:

```graphql
query {
  posts {
    ... on PublicBlog {
        id
        author
        title
    }
  }
}
```

The response would include an `"UNAUTHORIZED_FIELD_OR_TYPE"` error at the `/posts/... on PrivateBlog` path.

## Query deduplication

You can enable [query deduplication](/router/configuration/traffic-shaping/#query-deduplication) in the router to reduce redundant requests to a subgraph. The router does this by buffering similar queries and reusing the result.

**Query deduplication takes authorization into account.** First, the router groups unauthenticated queries together. Then it groups authenticated queries by their required scope set. It uses these groups to execute queries efficiently when fulfilling requests.

## Introspection

Introspection is turned off in the router by default, [as is best production practice](https://www.apollographql.com/blog/graphql/security/why-you-should-disable-graphql-introspection-in-production/). If you've chosen to [enable it](/router/configuration/overview/#introspection), keep in mind that **authorization directives don't affect introspection**. All fields that require authorization remain visible. However, directives applied to fields _aren't_ visible. If introspection might reveal too much information about internal types, then be sure it hasn't been enabled in your router configuration.

With introspection turned off, you can use GraphOS's [schema registry](/graphos/delivery/) to explore your supergraph schema and empower your teammates to do the same. If you want to completely remove fields from a graph rather than just preventing access (even with introspection on), consider building a [contract graph](/graphos/delivery/contracts/).

## Configuration options

The behavior of the authorization plugin can be modified with various options.

### reject_unauthorized

The `reject_unauthorized` option configures whether to reject an entire query if any authorization directive failed, or any part of the query was filtered by authorization directives. When enabled, a response contains the list of paths that are affected.

```yaml title="router.yaml"
authorization:
  directives:
    enabled: true
    reject_unauthorized: true # default: false
```

### errors

By default, when part of a query is filtered by authorization, the list of filtered paths is added to the response and logged by the router. This behavior can be customized for your needs.

#### log

By enabling the `log` option, you can choose if query filtering will result in a log event being output.

```yaml title="router.yaml"
authorization:
  directives:
    errors:
      log: false # default: true
```

<Note>

The `log` option should be disabled if filtering parts of queries according to the client's rights is approved as normal operation by platform operators.

</Note>

#### response

You can configure `response` to define what part of the GraphQL response should include filtered paths:

- `errors` (default) : place filtered paths in GraphQL errors
- `extensions`: place filtered paths in extensions. Useful to suppress exceptions on the client side while still giving information that parts of the query were filtered
- `disabled`: suppress all information that the query was filtered.

```yaml title="router.yaml"
authorization:
  directives:
    errors:
      response: "errors" # possible values: "errors" (default), "extensions", "disabled"
```

### dry_run

The `dry_run` option allows you to execute authorization directives without modifying a query, and evaluate the impact of authorization policies without interfering with existing traffic. It generates and returns the list of unauthorized paths as part of the response.

```yaml title="router.yaml"
authorization:
  directives:
    enabled: true
    dry_run: true # default: false
```

## Additional resources

Refer to the guide on [authenticating requests with the GraphOS Router](/graphos/routing/security/router-authentication) for an overview of authorization and authentication techniques.

- See the Apollo Solutions [auth coprocessor example](https://github.com/apollosolutions/example-coprocessor-custom-auth-directive) for how to set up a JavaScript coprocessor that applies custom auth checks.
- See the Apollo Solutions [`@policy` coprocessor example](https://github.com/apollosolutions/example-coprocessor-auth-policy) for how to set up a JavaScript coprocessor that evaluates policy-based authorization
- See the following Apollo Solutions repositories for examples of how to use JWT authentication with the `@requiresScopes` directive:
  - [Standard JWT authentication and authorization](https://github.com/apollosolutions/example-jwtauthentication)
  - [Non-standard authorization](https://github.com/apollosolutions/example-rhai-normalizejwtscopes) with Rhai scripts

<SolutionsNote />
